#include <EditorFramework/EditorFrameworkPCH.h>

#include <EditorFramework/Assets/AssetDocument.h>
#include <EditorFramework/DocumentWindow/EngineDocumentWindow.moc.h>
#include <EditorFramework/DocumentWindow/EngineViewWidget.moc.h>
#include <EditorFramework/InputContexts/EditorInputContext.h>
#include <EditorFramework/Preferences/EditorPreferences.h>
#include <Foundation/Utilities/GraphicsUtils.h>
#include <GuiFoundation/ActionViews/ToolBarActionMapView.moc.h>

ezUInt32 ezQtEngineViewWidget::s_uiNextViewID = 0;

ezQtEngineViewWidget::InteractionContext ezQtEngineViewWidget::s_InteractionContext;

void ezObjectPickingResult::Reset()
{
  m_PickedComponent = ezUuid();
  m_PickedObject = ezUuid();
  m_PickedOther = ezUuid();
  m_uiPartIndex = 0;
  m_vPickedPosition.SetZero();
  m_vPickedNormal.SetZero();
  m_vPickingRayStart.SetZero();
}

/// \brief Small helper class which exposes the native surface that the renderer can render into.
class ezQtNativeSurfaceWidget : public QWidget
{
public:
  ezQtNativeSurfaceWidget(QWidget* pParent = nullptr)
    : QWidget(pParent)
  {
    // setAttribute(Qt::WA_OpaquePaintEvent);
    setAutoFillBackground(false);
    setMouseTracking(true);
    setMinimumSize(64, 64); // prevent the window from becoming zero sized, otherwise the rendering code may crash

    setAttribute(Qt::WA_PaintOnScreen, true);
    setAttribute(Qt::WA_NativeWindow, true);
    setAttribute(Qt::WA_NoSystemBackground);
  }

  virtual void paintEvent(QPaintEvent* pEvent) override {}
  virtual QPaintEngine* paintEngine() const override { return nullptr; }
};

////////////////////////////////////////////////////////////////////////
// ezQtEngineViewWidget public functions
////////////////////////////////////////////////////////////////////////

ezSizeU32 ezQtEngineViewWidget::s_FixedResolution(0, 0);

ezQtEngineViewWidget::ezQtEngineViewWidget(QWidget* pParent, ezQtEngineDocumentWindow* pDocumentWindow, ezEngineViewConfig* pViewConfig)
  : QWidget(pParent)
  , m_pDocumentWindow(pDocumentWindow)
  , m_pViewConfig(pViewConfig)
{
  setAutoFillBackground(false);
  setMouseTracking(true);
  setMinimumSize(64, 64);
  setFocusPolicy(Qt::FocusPolicy::StrongFocus);

  m_pMainLayout = new QHBoxLayout(this);
  m_pMainLayout->setContentsMargins(0, 0, 0, 0);
  setLayout(m_pMainLayout);

  RecreateEngineViewport();

  m_bUpdatePickingData = false;
  m_bInDragAndDropOperation = false;

  m_uiViewID = s_uiNextViewID;
  ++s_uiNextViewID;

  m_fCameraLerp = 1.0f;
  m_fCameraTargetFovOrDim = 70.0f;

  ezEditorEngineProcessConnection::s_Events.AddEventHandler(ezMakeDelegate(&ezQtEngineViewWidget::EngineViewProcessEventHandler, this));

  if (ezEditorEngineProcessConnection::GetSingleton()->IsProcessCrashed())
    ShowRestartButton(true);
}


ezQtEngineViewWidget::~ezQtEngineViewWidget()
{
  ezEditorEngineProcessConnection::s_Events.RemoveEventHandler(ezMakeDelegate(&ezQtEngineViewWidget::EngineViewProcessEventHandler, this));

  {
    // Ensure the engine process swap chain is destroyed before the window.
    ezViewDestroyedMsgToEngine msg;
    msg.m_uiViewID = GetViewID();
    // If we fail to send the message the engine process is down and we don't need to clean up.
    if (m_pDocumentWindow->GetDocument()->SendMessageToEngine(&msg))
    {
      // Wait for engine process response
      auto callback = [&](ezProcessMessage* pMsg) -> bool
      {
        auto pResponse = static_cast<ezViewDestroyedResponseMsgToEditor*>(pMsg);
        return pResponse->m_DocumentGuid == m_pDocumentWindow->GetDocument()->GetGuid() && pResponse->m_uiViewID == msg.m_uiViewID;
      };
      ezProcessCommunicationChannel::WaitForMessageCallback cb = callback;

      if (ezEditorEngineProcessConnection::GetSingleton()->WaitForMessage(ezGetStaticRTTI<ezViewDestroyedResponseMsgToEditor>(), ezTime::MakeFromSeconds(5), &cb).Failed())
      {
        ezLog::Error("Timeout while waiting for engine process to destroy view.");
      }
    }
  }

  m_pDocumentWindow->RemoveViewWidget(this);
}

void ezQtEngineViewWidget::SyncToEngine()
{
  ezViewRedrawMsgToEngine cam;
  cam.m_uiRenderMode = m_pViewConfig->m_RenderMode;

  float fov = m_pViewConfig->m_Camera.GetFovOrDim();
  if (m_pViewConfig->m_Camera.IsPerspective())
  {
    ezEditorPreferencesUser* pPref = ezPreferences::QueryPreferences<ezEditorPreferencesUser>();
    fov = pPref->m_fPerspectiveFieldOfView;
  }

  cam.m_uiViewID = GetViewID();
  cam.m_fNearPlane = m_pViewConfig->m_Camera.GetNearPlane();
  cam.m_fFarPlane = m_pViewConfig->m_Camera.GetFarPlane();
  cam.m_iCameraMode = (ezInt8)m_pViewConfig->m_Camera.GetCameraMode();
  cam.m_bUseCameraTransformOnDevice = m_pViewConfig->m_bUseCameraTransformOnDevice;
  cam.m_fFovOrDim = fov;
  cam.m_vDirForwards = m_pViewConfig->m_Camera.GetCenterDirForwards();
  cam.m_vDirUp = m_pViewConfig->m_Camera.GetCenterDirUp();
  cam.m_vDirRight = m_pViewConfig->m_Camera.GetCenterDirRight();
  cam.m_vPosition = m_pViewConfig->m_Camera.GetCenterPosition();
  cam.m_ViewMatrix = m_pViewConfig->m_Camera.GetViewMatrix();
  m_pViewConfig->m_Camera.GetProjectionMatrix((float)m_pViewportWidget->width() / (float)m_pViewportWidget->height(), cam.m_ProjMatrix);

  cam.m_uiHWND = (ezUInt64)(m_pViewportWidget->winId());
  cam.m_uiWindowWidth = m_pViewportWidget->width() * this->devicePixelRatio();
  cam.m_uiWindowHeight = m_pViewportWidget->height() * this->devicePixelRatio();
  cam.m_bUpdatePickingData = m_bUpdatePickingData;
  cam.m_bEnablePickingSelected = IsPickingAgainstSelectionAllowed() && (!ezEditorInputContext::IsAnyInputContextActive() || ezEditorInputContext::GetActiveInputContext()->IsPickingSelectedAllowed());
  cam.m_bEnablePickTransparent = m_bPickTransparent;

  if (s_FixedResolution.HasNonZeroArea())
  {
    cam.m_uiWindowWidth = s_FixedResolution.width;
    cam.m_uiWindowHeight = s_FixedResolution.height;
  }

  m_pDocumentWindow->GetEditorEngineConnection()->SendMessage(&cam);
}


void ezQtEngineViewWidget::GetCameraMatrices(ezMat4& out_mViewMatrix, ezMat4& out_mProjectionMatrix) const
{
  out_mViewMatrix = m_pViewConfig->m_Camera.GetViewMatrix();
  m_pViewConfig->m_Camera.GetProjectionMatrix((float)m_pViewportWidget->width() / (float)m_pViewportWidget->height(), out_mProjectionMatrix);
}

void ezQtEngineViewWidget::UpdateCameraInterpolation()
{
  if (m_fCameraLerp >= 1.0f)
    return;

  const ezTime tNow = ezTime::Now();
  const ezTime tDiff = tNow - m_LastCameraUpdate;
  m_LastCameraUpdate = tNow;

  m_fCameraLerp += tDiff.GetSeconds() * 3.0f;

  if (m_fCameraLerp >= 1.0f)
    m_fCameraLerp = 1.0f;

  ezCamera& cam = m_pViewConfig->m_Camera;

  const float fLerpValue = ezMath::Sin(ezAngle::MakeFromDegree(90.0f * m_fCameraLerp));

  ezQuat qRot, qRotFinal;
  qRot = ezQuat::MakeShortestRotation(m_vCameraStartDirection, m_vCameraTargetDirection);
  qRotFinal = ezQuat::MakeSlerp(ezQuat::MakeIdentity(), qRot, fLerpValue);

  const ezVec3 vNewDirection = qRotFinal * m_vCameraStartDirection;
  const ezVec3 vNewPosition = ezMath::Lerp(m_vCameraStartPosition, m_vCameraTargetPosition, fLerpValue);
  const float fNewFovOrDim = ezMath::Lerp(m_fCameraStartFovOrDim, m_fCameraTargetFovOrDim, fLerpValue);

  /// \todo Hard coded up vector
  cam.LookAt(vNewPosition, vNewPosition + vNewDirection, m_vCameraUp);
  cam.SetCameraMode(cam.GetCameraMode(), fNewFovOrDim, cam.GetNearPlane(), cam.GetFarPlane());
}

void ezQtEngineViewWidget::InterpolateCameraTo(const ezVec3& vPosition, const ezVec3& vDirection, float fFovOrDim, const ezVec3* pNewUpDirection /*= nullptr*/, bool bImmediate /*= false*/)
{
  m_vCameraStartPosition = m_pViewConfig->m_Camera.GetPosition();
  m_vCameraTargetPosition = vPosition;

  m_vCameraStartDirection = m_pViewConfig->m_Camera.GetCenterDirForwards();
  m_vCameraTargetDirection = vDirection;

  if (pNewUpDirection)
    m_vCameraUp = *pNewUpDirection;
  else
    m_vCameraUp = m_pViewConfig->m_Camera.GetCenterDirUp();

  m_vCameraStartDirection.Normalize();
  m_vCameraTargetDirection.Normalize();
  m_vCameraUp.Normalize();


  m_fCameraStartFovOrDim = m_pViewConfig->m_Camera.GetFovOrDim();

  if (fFovOrDim > 0.0f)
    m_fCameraTargetFovOrDim = fFovOrDim;


  EZ_ASSERT_DEV(m_fCameraTargetFovOrDim > 0, "Invalid FOV or ortho dimension");

  if (m_vCameraStartPosition == m_vCameraTargetPosition && m_vCameraStartDirection == m_vCameraTargetDirection && m_fCameraStartFovOrDim == m_fCameraTargetFovOrDim)
    return;

  m_LastCameraUpdate = ezTime::Now();
  m_fCameraLerp = 0.0f;

  if (bImmediate)
  {
    // make sure the next camera update interpolates all the way
    m_LastCameraUpdate -= ezTime::MakeFromSeconds(10);
    m_fCameraLerp = 0.9f;
  }
}

void ezQtEngineViewWidget::SetEnablePicking(bool bEnable)
{
  m_bUpdatePickingData = bEnable;
}

void ezQtEngineViewWidget::SetPickTransparent(bool bEnable)
{
  if (m_bPickTransparent == bEnable)
    return;

  m_bPickTransparent = bEnable;
  m_LastPickingResult.Reset();
}

void ezQtEngineViewWidget::OpenContextMenu(QPoint globalPos)
{
  s_InteractionContext.m_pLastHoveredViewWidget = this;
  s_InteractionContext.m_pLastPickingResult = &m_LastPickingResult;

  OnOpenContextMenu(globalPos);
}


const ezObjectPickingResult& ezQtEngineViewWidget::PickObject(ezUInt16 uiScreenPosX, ezUInt16 uiScreenPosY) const
{
  if (!ezEditorEngineProcessConnection::GetSingleton()->IsEngineSetup())
  {
    m_LastPickingResult.Reset();
  }
  else
  {
    ezViewPickingMsgToEngine msg;
    msg.m_uiViewID = GetViewID();
    msg.m_uiPickPosX = uiScreenPosX * devicePixelRatio();
    msg.m_uiPickPosY = uiScreenPosY * devicePixelRatio();

    GetDocumentWindow()->GetDocument()->SendMessageToEngine(&msg);
  }

  return m_LastPickingResult;
}


ezResult ezQtEngineViewWidget::PickPlane(ezUInt16 uiScreenPosX, ezUInt16 uiScreenPosY, const ezPlane& plane, ezVec3& out_vPosition) const
{
  const auto& cam = m_pViewConfig->m_Camera;

  ezMat4 mView = cam.GetViewMatrix();
  ezMat4 mProj;
  cam.GetProjectionMatrix((float)m_pViewportWidget->width() / (float)m_pViewportWidget->height(), mProj);
  ezMat4 mViewProj = mProj * mView;
  ezMat4 mInvViewProj = mViewProj.GetInverse();

  ezVec3 vScreenPos(uiScreenPosX, uiScreenPosY, 0);
  ezVec3 vResPos, vResRay;

  if (ezGraphicsUtils::ConvertScreenPosToWorldPos(mInvViewProj, 0, 0, m_pViewportWidget->width(), m_pViewportWidget->height(), vScreenPos, vResPos, &vResRay).Failed())
    return EZ_FAILURE;

  if (plane.GetRayIntersection(vResPos, vResRay, nullptr, &out_vPosition))
    return EZ_SUCCESS;

  return EZ_FAILURE;
}

void ezQtEngineViewWidget::HandleViewMessage(const ezEditorEngineViewMsg* pMsg)
{
  if (const ezViewPickingResultMsgToEditor* pFullMsg = ezDynamicCast<const ezViewPickingResultMsgToEditor*>(pMsg))
  {
    m_LastPickingResult.m_PickedObject = pFullMsg->m_ObjectGuid;
    m_LastPickingResult.m_PickedComponent = pFullMsg->m_ComponentGuid;
    m_LastPickingResult.m_PickedOther = pFullMsg->m_OtherGuid;
    m_LastPickingResult.m_uiPartIndex = pFullMsg->m_uiPartIndex;
    m_LastPickingResult.m_vPickedPosition = pFullMsg->m_vPickedPosition;
    m_LastPickingResult.m_vPickedNormal = pFullMsg->m_vPickedNormal;
    m_LastPickingResult.m_vPickingRayStart = pFullMsg->m_vPickingRayStartPosition;

    return;
  }
  else if (const ezViewMarqueePickingResultMsgToEditor* pFullMsg = ezDynamicCast<const ezViewMarqueePickingResultMsgToEditor*>(pMsg))
  {
    HandleMarqueePickingResult(pFullMsg);
    return;
  }
}

ezPlane ezQtEngineViewWidget::GetFallbackPickingPlane(ezVec3 vPointOnPlane) const
{
  if (m_pViewConfig->m_Camera.IsPerspective())
  {
    return ezPlane::MakeFromNormalAndPoint(ezVec3(0, 0, 1), vPointOnPlane);
  }
  else
  {
    return ezPlane::MakeFromNormalAndPoint(-m_pViewConfig->m_Camera.GetCenterDirForwards(), vPointOnPlane);
  }
}

void ezQtEngineViewWidget::TakeScreenshot(const char* szOutputPath) const
{
  ezViewScreenshotMsgToEngine msg;
  msg.m_uiViewID = GetViewID();
  msg.m_sOutputFile = szOutputPath;
  m_pDocumentWindow->GetDocument()->SendMessageToEngine(&msg);
}

////////////////////////////////////////////////////////////////////////
// ezQtEngineViewWidget qt overrides
////////////////////////////////////////////////////////////////////////

bool ezQtEngineViewWidget::eventFilter(QObject* object, QEvent* event)
{
  if (event->type() == QEvent::Type::ShortcutOverride)
  {
    if (ezEditorInputContext::IsAnyInputContextActive())
    {
      // if the active input context does not like other shortcuts,
      // accept this event and thus block further shortcut processing
      // instead Qt will then send a keypress event
      if (ezEditorInputContext::GetActiveInputContext()->GetShortcutsDisabled())
        event->accept();
    }
  }

  return false;
}


void ezQtEngineViewWidget::paintEvent(QPaintEvent* event)
{
  // event->accept();
}

void ezQtEngineViewWidget::resizeEvent(QResizeEvent* event)
{
  m_pDocumentWindow->TriggerRedraw();
}

void ezQtEngineViewWidget::keyPressEvent(QKeyEvent* e)
{
  if (e->isAutoRepeat())
    return;

  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->KeyPressEvent(e) == ezEditorInput::WasExclusivelyHandled)
      return;
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
    return;

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->KeyPressEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->KeyPressEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
      return;
  }

  QWidget::keyPressEvent(e);
}

void ezQtEngineViewWidget::keyReleaseEvent(QKeyEvent* e)
{
  if (e->isAutoRepeat())
    return;

  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->KeyReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled)
      return;
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
    return;

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->KeyReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->KeyReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
      return;
  }

  QWidget::keyReleaseEvent(e);
}

void ezQtEngineViewWidget::mousePressEvent(QMouseEvent* e)
{
  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->MousePressEvent(e) == ezEditorInput::WasExclusivelyHandled)
    {
      e->accept();
      return;
    }
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    e->accept();
    return;
  }

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->MousePressEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->MousePressEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
    {
      e->accept();
      return;
    }
  }

  QWidget::mousePressEvent(e);
}

void ezQtEngineViewWidget::mouseReleaseEvent(QMouseEvent* e)
{
  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->MouseReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled)
    {
      e->accept();
      return;
    }
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    e->accept();
    return;
  }

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->MouseReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->MouseReleaseEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
    {
      e->accept();
      return;
    }
  }

  QWidget::mouseReleaseEvent(e);
}

void ezQtEngineViewWidget::mouseMoveEvent(QMouseEvent* e)
{
  s_InteractionContext.m_pLastHoveredViewWidget = this;
  s_InteractionContext.m_pLastPickingResult = &m_LastPickingResult;

  // kick off the picking
  PickObject(e->pos().x(), e->pos().y());

  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->MouseMoveEvent(e) == ezEditorInput::WasExclusivelyHandled)
    {
      e->accept();
      return;
    }
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    e->accept();
    return;
  }

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->MouseMoveEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->MouseMoveEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
    {
      e->accept();
      return;
    }
  }

  QWidget::mouseMoveEvent(e);
}

void ezQtEngineViewWidget::wheelEvent(QWheelEvent* e)
{
  // if a context is active, it gets exclusive access to the input data
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    if (ezEditorInputContext::GetActiveInputContext()->WheelEvent(e) == ezEditorInput::WasExclusivelyHandled)
      return;
  }

  if (ezEditorInputContext::IsAnyInputContextActive())
    return;

  // Override context
  {
    ezEditorInputContext* pOverride = GetDocumentWindow()->GetDocument()->GetEditorInputContextOverride();
    if (pOverride != nullptr)
    {
      if (pOverride->WheelEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
        return;
    }
  }

  // if no context is active, pass the input through in a certain order, until someone handles it
  for (auto pContext : m_InputContexts)
  {
    if (pContext->WheelEvent(e) == ezEditorInput::WasExclusivelyHandled || ezEditorInputContext::IsAnyInputContextActive())
      return;
  }

  QWidget::wheelEvent(e);
}

void ezQtEngineViewWidget::focusOutEvent(QFocusEvent* e)
{
  if (ezEditorInputContext::IsAnyInputContextActive())
  {
    ezEditorInputContext::GetActiveInputContext()->FocusLost(false);
    ezEditorInputContext::SetActiveInputContext(nullptr);
  }

  QWidget::focusOutEvent(e);
}


void ezQtEngineViewWidget::dragEnterEvent(QDragEnterEvent* e)
{
  m_bInDragAndDropOperation = true;
}


void ezQtEngineViewWidget::dragLeaveEvent(QDragLeaveEvent* e)
{
  m_bInDragAndDropOperation = false;
}


void ezQtEngineViewWidget::dropEvent(QDropEvent* e)
{
  m_bInDragAndDropOperation = false;
}


////////////////////////////////////////////////////////////////////////
// ezQtEngineViewWidget protected functions
////////////////////////////////////////////////////////////////////////

void ezQtEngineViewWidget::EngineViewProcessEventHandler(const ezEditorEngineProcessConnection::Event& e)
{
  switch (e.m_Type)
  {
    case ezEditorEngineProcessConnection::Event::Type::ProcessCrashed:
    {
      ShowRestartButton(true);
    }
    break;

    case ezEditorEngineProcessConnection::Event::Type::ProcessStarted:
    {
      RecreateEngineViewport();
      ShowRestartButton(false);
    }
    break;

    case ezEditorEngineProcessConnection::Event::Type::ProcessShutdown:
      break;

    case ezEditorEngineProcessConnection::Event::Type::ProcessMessage:
      break;

    case ezEditorEngineProcessConnection::Event::Type::Invalid:
      EZ_ASSERT_DEV(false, "Invalid message should never happen");
      break;

    case ezEditorEngineProcessConnection::Event::Type::ProcessRestarted:
      break;
  }
}

void ezQtEngineViewWidget::ShowRestartButton(bool bShow)
{
  ezQtScopedUpdatesDisabled _(this);

  if (m_pRestartButton == nullptr && bShow == true)
  {
    m_pRestartButton = new QPushButton(this);
    m_pRestartButton->setText("Restart Engine View Process");
    m_pRestartButton->setVisible(ezEditorEngineProcessConnection::GetSingleton()->IsProcessCrashed());
    m_pRestartButton->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    m_pRestartButton->connect(m_pRestartButton, &QPushButton::clicked, this, &ezQtEngineViewWidget::SlotRestartEngineProcess);

    m_pMainLayout->addWidget(m_pRestartButton);
  }

  if (m_pRestartButton)
  {
    m_pRestartButton->setVisible(bShow);

    if (bShow)
      m_pRestartButton->update();
  }

  m_pViewportWidget->setVisible(!bShow);
}

void ezQtEngineViewWidget::RecreateEngineViewport()
{
  if (m_pViewportWidget)
  {
    m_pViewportWidget->removeEventFilter(this);
    m_pViewportWidget->hide();
    m_pViewportWidget->setParent(nullptr);
    m_pViewportWidget->deleteLater();
  }

  m_pViewportWidget = new ezQtNativeSurfaceWidget(this);
  m_pViewportWidget->installEventFilter(this);
  m_pViewportWidget->setFocusProxy(this);
  if (s_FixedResolution.HasNonZeroArea())
  {
    qreal pixelRatio = devicePixelRatio();
    // When using DPI scaling, this could actually not be possible to achieve so we use the ceiling of the logical size. This is fine, as the editor tests crop the resulting image if not of the proper size.
    m_pViewportWidget->setFixedSize(
      static_cast<ezInt32>(ezMath::Ceil(s_FixedResolution.width / pixelRatio)),
      static_cast<ezInt32>(ezMath::Ceil(s_FixedResolution.height / pixelRatio)));
  }
  else
  {
    m_pMainLayout->addWidget(m_pViewportWidget);
  }
}

////////////////////////////////////////////////////////////////////////
// ezQtEngineViewWidget private slots
////////////////////////////////////////////////////////////////////////

void ezQtEngineViewWidget::SlotRestartEngineProcess()
{
  ezEditorEngineProcessConnection::GetSingleton()->RestartProcess().IgnoreResult();
}


////////////////////////////////////////////////////////////////////////
// ezQtViewWidgetContainer
////////////////////////////////////////////////////////////////////////

ezQtViewWidgetContainer::ezQtViewWidgetContainer(ads::CDockManager* pDockManager, QWidget* pParent, ezQtEngineViewWidget* pViewWidget, const char* szToolBarMapping)
  : ads::CDockWidget(pDockManager, "3D View", pParent)
{
  setObjectName("ezQtViewWidgetContainer");

  setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetClosable, false);
  setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetFloatable, false);
  setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetMovable, false);
  setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetFocusable, true);

  // need contrast with the rest of the widgets around it
  setBackgroundRole(QPalette::Base);
  setAutoFillBackground(true);

  QWidget* pDummy = new QWidget();
  pDummy->setObjectName("Dummy");

  m_pLayout = new QVBoxLayout(pDummy);
  m_pLayout->setObjectName("QVBoxLayout1");
  m_pLayout->setContentsMargins(0, 0, 0, 0);
  m_pLayout->setSpacing(0);
  pDummy->setLayout(m_pLayout);

  m_pViewWidget = pViewWidget;
  m_pViewWidget->setParent(pDummy);

  if (!ezStringUtils::IsNullOrEmpty(szToolBarMapping))
  {
    // Add Tool Bar
    ezQtToolBarActionMapView* pToolBar = new ezQtToolBarActionMapView("Toolbar", this);
    ezActionContext context;
    context.m_sMapping = szToolBarMapping;
    context.m_pDocument = pViewWidget->GetDocumentWindow()->GetDocument();
    context.m_pWindow = m_pViewWidget;
    pToolBar->SetActionContext(context);
    m_pLayout->addWidget(pToolBar, 0);
  }

  m_pLayout->addWidget(m_pViewWidget, 1);

  setWidget(pDummy);
}

ezQtViewWidgetContainer::~ezQtViewWidgetContainer() = default;
